package com.hq.jta;


import com.mysql.cj.jdbc.MysqlXADataSource;
import com.mysql.cj.jdbc.MysqlXid;

import javax.sql.XAConnection;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * 分布式事务jta 遵循xa定义的分布式事务规范  mysql oracle 都是实现了XA协议 https://www.bilibili.com/video/av74188456/?p=8
 * JTA:面向应用，应用服务器与资源管理器的高层事务接口。
 * 分布式事务框架 atmoickos (e托米克) 解决分库的事务问题
 * <p>
 * TCC（解决微服务相关）：
 * <p>
 * 第一阶段：
 * try    对应 jta 预处理sql  所有资源进行对应的操作完成后，把结果进行提交给事务管理器（预提交）
 * 第二阶段：
 * confirm 对应 jta commit    资源管理器得到所有资源结果正常后，进行最终提交
 * cancle  对应 jta rollback  资源管理器得到结果不符合预期，进行回滚。
 * <p>
 * CAP： Consistency 一致性   Availability 可用性 Partition tolerance分区容错性
 * <p>
 * 缺点：
 * 1、整个过程，数据被Lock的时间跨越整个事务，直到全局事务结束
 * 2、当业务规模越来越大的情况下，2PC的局限性就越来越明显
 * 3、与本地事务相比，XA协议的系统开销很大
 * 会出现的问题：网络异常。
 *
 * @author hq
 */
public class JtaTarnsaction {
    public static void main(String[] args) {
        String connStr1 = "jdbc:mysql://localhost:3306/bingfa?serverTimezone=UTC&useSSL=false";
        String connStr2 = "jdbc:mysql://localhost:3306/renren-fast?serverTimezone=UTC&useSSL=false";

        //从不同的数据库中，获取数据源。
        MysqlXADataSource ds1 = getDataSource(connStr1, "root", "root");
        MysqlXADataSource ds2 = getDataSource(connStr2, "root", "root");

        try {
            //==========从数据库1中获取连接 获取statement对象 操作数据库1==========
            XAConnection xaConnection1 = ds1.getXAConnection();
            //拿到资源管理器1
            XAResource xaResource1 = xaConnection1.getXAResource();
            Connection connection1 = xaConnection1.getConnection();
            Statement statement1 = connection1.createStatement();
            //从数据库2中获取连接 获取statement对象 操作数据库2
            XAConnection xaConnection2 = ds2.getXAConnection();
            //拿到资源管理器2
            XAResource xaResource2 = xaConnection2.getXAResource();
            Connection connection2 = xaConnection2.getConnection();
            Statement statement2 = connection2.createStatement();

            //创建分布式事务id 几个资源就对应几个id.
            MysqlXid mysqlXid1 = new MysqlXid(new byte[]{0x01}, new byte[]{0x02}, 100);
            MysqlXid mysqlXid2 = new MysqlXid(new byte[]{0x011}, new byte[]{0x012}, 1000);

            //1、=========事务分支1  预执行sql语句（比如预扣库存  等commit后才完成扣库存）========
            // 发起一个分布式事务，给与一个事务ID。或者将某个process关联到已存在的分布式事务
            xaResource1.start(mysqlXid1, XAResource.TMNOFLAGS);
            int result1 = statement1.executeUpdate("INSERT INTO `bingfa`.`user`(`name`, `age`) VALUES ('jta3', 100);");
            //将process从关联的分布式事务中剥离
            xaResource1.end(mysqlXid1, XAResource.TMSUCCESS);
            //事务分支2  预执行sql语句
            xaResource2.start(mysqlXid2, XAResource.TMNOFLAGS);
            int result2 = statement2.executeUpdate("INSERT INTO `renren-fast`.`jta_test`(`username`, `password`) VALUES ('jta3', '100')");
            xaResource2.end(mysqlXid2, XAResource.TMSUCCESS);

            //2、==========两阶段提交协议第一阶段 预提交=========
            //要求参与到分布式事务的RM准备提交事务分支 2PC的第1阶段
            int ret1 = xaResource1.prepare(mysqlXid1);
            int ret2 = xaResource2.prepare(mysqlXid2);

            //3、=====两阶段提交协议第二阶段 2者都执行成功后 才进行事务的提交=====
            //要求参与到分布式事务的RM提交事务分支 2PC的第2阶段
            if (ret1 == XAResource.XA_OK && ret2 == XAResource.XA_OK) {
                xaResource1.commit(mysqlXid1, false);
                xaResource2.commit(mysqlXid2, false);
                System.out.println("result1:" + result1 + "======" + "result2:" + result2);
            } else {
                //回滚
                xaResource1.rollback(mysqlXid1);
                xaResource2.rollback(mysqlXid2);
                System.out.println("回滚");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (XAException e) {
            e.printStackTrace();
        }


    }

    private static MysqlXADataSource getDataSource(String connStr1, String user, String password) {
        MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
        mysqlXADataSource.setURL(connStr1);
        mysqlXADataSource.setUser(user);
        mysqlXADataSource.setPassword(password);
        return mysqlXADataSource;
    }
}
